import 'dart:async';
import 'dart:convert';

import 'package:detect/utils/toast_util.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:log4f/log4f.dart';
import 'package:web_socket_channel/io.dart';

typedef WsInitCallback = Future<WSUtil?> Function(WSUtil? wsUtil);
typedef WsErrorCallback = void Function(Object error);
typedef WsFailCallback = void Function(WSUtil instance, Map<String, dynamic> response);
typedef WsSuccessResult = bool Function(Map<String, dynamic> response);

class WSUtil {
  static final Map<String, WSUtil> _caches = {};
  final Duration _heartDuration = const Duration(seconds: 10);
  final String url;
  final String _cmdField;
  final _HeartData _heart;
  final List<ReceiveMsgListener> _listens;
  final WsInitCallback? _onInit;
  final WsErrorCallback? _onError;
  final WsFailCallback? _onFail;
  final WsSuccessResult _onSuccess;
  final List<String>? _protocols;
  final Map<String, dynamic>? _headers;
  bool connectState = true;
  StreamSubscription? _subscriptions;
  IOWebSocketChannel? _instance;
  Timer? _heartTimer;

  WSUtil._(
    this.url,
    this._cmdField,
    this._heart,
    this._onSuccess,
    this._listens,
    this._onInit,
    this._onError,
    this._onFail,
    this._protocols,
    this._headers,
  ) {
    _instance = IOWebSocketChannel.connect(Uri.parse(url), protocols: _protocols, headers: _headers, pingInterval: _heartDuration, connectTimeout: _heartDuration);
    _subscriptions = _instance?.stream.listen(
      (message) async {
        message = message is List<int> ? utf8.decode(Uint8List.fromList(message)) : message;
        // debugPrint("接收消息 $url => $message");
        Log4f.i(msg: "$url => $message", tag: "接收消息");
        Map<String, dynamic> response = jsonDecode(message);
        if (_onSuccess(response)) {
          Object? cmd = response[_cmdField];
          for (ReceiveMsgListener receive in _listens.toList()) {
            if (receive.cmds.contains(cmd)) {
              dynamic result = await receive.listener(response);
              _listens.removeWhere((element) => element.id == cmd && result != false);
            }
          }
        } else {
          _onFail?.call(this, response);
          ToastUtil.failure(response['msg'] ?? response['message'] ?? '请求失败');
        }
      },
      onDone: () {
        // debugPrint("链接断开 $url");
        Log4f.e(msg: url, tag: "链接断开");
        connectState = false;
      },
      onError: (error) {
        close();
        _onError?.call(error);
        // debugPrint("无法连接到服务 => $url");
        Log4f.e(msg: url, tag: "无法连接到服务");
      },
      cancelOnError: true,
    );
  }

  factory WSUtil._init(
    String url,
    String cmdField,
    _HeartData heart,
    WsSuccessResult onSuccess, {
    WsInitCallback? onInit,
    WsInitCallback? onReconnect,
    WsErrorCallback? onError,
    WsFailCallback? onFail,
    List<String> protocols = const <String>[],
    Map<String, dynamic>? headers,
  }) {
    if (!_caches.containsKey(url) || !(_caches[url]?.connectState ?? false)) {
      List<ReceiveMsgListener> listens = _caches[url]?._listens ?? [];
      _caches[url]?.close();
      _caches[url] = WSUtil._(url, cmdField, heart, onSuccess, listens, onInit, onError, onFail, protocols, headers);
      onInit?.call(_caches[url]).then((wsUtil) => onReconnect?.call(wsUtil));
    }
    return _caches[url]!;
  }

  ///心跳
  WSUtil _startHeart() {
    if (_heart.data.isEmpty || !connectState) return this;
    _heartTimer?.cancel();
    _heartTimer = Timer(_heartDuration, () => send(data: _heart.data)._startHeart());
    return this;
  }

  ///监听消息
  WSUtil listener(List<ReceiveMsgListener> listeners) {
    _listens.removeWhere((element) => listeners.map<Object>((e) => e.id).contains(element.id));
    _listens.addAll(listeners);
    return this;
  }

  ///发送消息
  WSUtil send({Map<String, dynamic> data = const {}, ReceiveMsgListener? response}) {
    if (data.isEmpty) return this;
    if (response != null) {
      _listens.removeWhere((element) => element.id == response.id);
      _listens.add(response);
    }
    if (connectState) {
      // debugPrint("发送消息 $url => $data");
      Log4f.i(msg: "$url => $data", tag: "发送消息");
      _instance?.sink.add(jsonEncode(data));
    } else {
      close();
      WSUtil._init(
        url,
        _cmdField,
        _heart,
        _onSuccess,
        onInit: _onInit,
        onReconnect: (wsUtil) async => wsUtil?.send(data: data, response: response),
        onError: _onError,
        onFail: _onFail,
        protocols: _protocols ?? [],
        headers: _headers,
      );
    }
    return this;
  }

  ///关闭链接
  Future<void> close() async {
    connectState = false;
    _heartTimer?.cancel();
    _caches.removeWhere((k, v) => k == url);
    await _subscriptions?.cancel();
    await _instance?.sink.close();
  }

  ///关闭所有链接
  static void closeAll() {
    for (WSUtil wsUtil in _caches.values) {
      wsUtil.close();
    }
  }

  ///获取缓存链接
  static WSUtil? get([String url = 'ws://192.168.1.2:8080/ws']) => _caches[url];

  ///默认链接
  static WSUtil connect([String url = 'ws://192.168.1.2:8080/ws']) => WSUtil._init(
        url,
        "type",
        _HeartData("beat", {'type': 'beat', 'msg': 'ping'}),
        (response) => true,
        onInit: (wsUtil) async {
          // debugPrint("链接$url");
          Log4f.i(msg: url, tag: "链接");
          wsUtil?._startHeart();
          return wsUtil;
        },
        onFail: (instance, response) => instance.close(),
      );
}

class ReceiveMsgListener {
  final Object id;
  final List<Object> cmds;
  final dynamic Function(Map<String, dynamic> response) listener;

  ReceiveMsgListener(this.id, this.listener, [List<Object>? cmds]) : cmds = cmds ?? [id];
}

class _HeartData {
  final Object id;
  final Map<String, dynamic> data;

  _HeartData(this.id, this.data);
}
